#include <winhack.h>
//#include <elasys_server.h>
#include <elapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysconfig.h>
#include <sharemem.h>

#include "camplus.h"

#define USED_RECORD_FREAMS 4
#define GET_VIDEOBUFFER_ID(n) (n & (USED_RECORD_FREAMS - 1))


void VideoRecorder::InitVideoRecorderFormat()
{
    // Though the setting of these values are not implemented in virtual marchine,
    // We can also set and get them so that the application can run without error.
    // Now set the default values.
    m_RecorderFormat.m_iVideoResolution      = CAMERA_VR_LOW;
    m_RecorderFormat.m_iFrameRate            = 0; // 0 means using the default value
    m_RecorderFormat.m_iZoom                 = 1;
    m_RecorderFormat.m_iBrightness           = 50;
    m_RecorderFormat.m_iContrast             = 50;
    m_RecorderFormat.m_iSharpness            = 0;
    m_RecorderFormat.m_iWhiteBalance         = CAMERA_WB_AUTO;
    m_RecorderFormat.m_iColorMode            = CAMERA_CM_COLOR;
    m_RecorderFormat.m_iSceneMode            = CAMERA_SM_AUTO;
    m_RecorderFormat.m_iDirection            = CAMERA_DR_HORIZONTAL;
    m_RecorderFormat.m_iFlash                = CAMERA_FL_CLOSED;
    m_RecorderFormat.m_iISO                  = 200;
    m_RecorderFormat.m_iExposureCompensation = 0;
}

void VideoRecorder::ResolutionToSize(int Resolution,
                      unsigned long *pWidth,
                      unsigned long *pHeight)
{
    switch (Resolution) {
        case CAMERA_VR_LOW:
            *pWidth  = 144;
            *pHeight = 176;
            break;
        case CAMERA_VR_MEDIUM:
        case CAMERA_VR_HIGH:
            *pWidth  = 240;
            *pHeight = 320;
            break;
        default:
            *pWidth  = 144;
            *pHeight = 176;
            break;
    }
}

ECode VideoRecorder::SetRecordSize()
{
    BOOL ret;

    ResolutionToSize(m_RecorderFormat.m_iVideoResolution,
                    &m_ulRecordFrameWidth,
                    &m_ulRecordFrameHeight);

    ret = m_pShower->SetShowFormat(m_ulRecordFrameWidth,
                                   m_ulRecordFrameHeight);
    if(!ret){
        printf("Error: VideoRecorder SetRecordSize failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return E_INVALID_OPERATION;
    }

    ret = m_pConverter->SetDesFormat(m_ulRecordFrameWidth,
                                     m_ulRecordFrameHeight);
    if(!ret){
        printf("Error: VideoRecorder SetRecordSize failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return E_INVALID_OPERATION;
    }

    ret = BuildRecordBuffer();
    if(!ret){
        printf("Error: VideoRecorder SetRecordSize failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return E_OUT_OF_MEMORY;
    }

    return NOERROR;
}

BOOL VideoRecorder::BuildRecordBuffer()
{
    m_ulRecordBufFrameLen = m_ulRecordFrameWidth
                         * m_ulRecordFrameHeight
                         * YCBCR_422_BYTES;

    if(NULL != m_pRecordFramedBuf){
        FreeAllocShareMem((Address)m_pRecordFramedBuf);
        m_pRecordFramedBuf = NULL;
    }
    ECode ec = AllocShareMem(m_ulRecordBufFrameLen * USED_RECORD_FREAMS,
                    MemoryProtection_RW,
                    (Address *)&m_pRecordFramedBuf, NULL);
    if (FAILED(ec)){
        printf("Error: VideoRecorder BuildRecordBuffer failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return FALSE;
    }



//    m_pRecordFramedBuf = (BYTE *)realloc(m_pRecordFramedBuf,
//        m_ulRecordBufFrameLen * USED_RECORD_FREAMS);
//    if (NULL == m_pRecordFramedBuf){
//        printf("Error: VideoRecorder BuildRecordBuffer failed!
//        file:%s. line:%d. \n",
//        __FILE__, __LINE__);
//        return FALSE;
//    }

    return TRUE;
}

BOOL VideoRecorder::Initialize(FrameCapture *pCap,
                               FrameDisplay *pShower,
                               RGB2YCbCrConverter *pConverter)
{
    assert(pCap);
    assert(pShower);
    assert(pConverter);

    InitVideoRecorderFormat();

    m_uOutFrameID      = 0;
    m_uInFrameID       = 0;

    m_pFrameCopyEvent  = NULL;

    m_pRecordFramedBuf = NULL;

    m_pCapture         = pCap;

    m_pShower          = pShower;

    m_pConverter       = pConverter;

    m_hRecordLock = CreateMutexW(NULL, FALSE, NULL);
    if(NULL == m_hRecordLock){
        printf("Error: camera VideoRecorder Initialize failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return FALSE;
    }

    //Init the zoom with recorder format
    m_pConverter->SetDigitalZoom(m_RecorderFormat.m_iZoom);


    if(FAILED(SetRecordSize())){
        printf("Error: camera VideoRecorder Initialize failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        CloseHandle(m_hRecordLock);
        m_hRecordLock = NULL;
        return FALSE;
    }

    return TRUE;
}

void  VideoRecorder::Destroy()
{
    //Incase the capture still open
    m_pCapture->StopCapture();

    m_pShower->ResetShowFormat();

    if(NULL != m_pRecordFramedBuf){
        FreeAllocShareMem((Address)m_pRecordFramedBuf);
        m_pRecordFramedBuf = NULL;
    }

    if(NULL != m_pFrameCopyEvent){
        m_pFrameCopyEvent->Release();
        m_pFrameCopyEvent = NULL;
    }

    CloseHandle(m_hRecordLock);
    m_hRecordLock = NULL;

}


ECode VideoRecorder::GetVideoRecorderFormat(
                        unsigned int nID, int *pwParam, int *plParam)
{
    ECode ec = NOERROR;

    switch (nID) {
    case CAMERA_FORMAT_VIDEO_RESOLUTION:
        *pwParam = m_RecorderFormat.m_iVideoResolution;
        break;
    //BUGBUG:The AppResolution width > Height, different from internal
    case CAMERA_FORMAT_VIDEO_GETSIZE:
        *pwParam = (int)m_ulRecordFrameHeight;
        *plParam =  (int)m_ulRecordFrameWidth;
        break;
    case CAMERA_FORMAT_ZOOM:
        *pwParam = m_RecorderFormat.m_iZoom;
        *plParam = 1;
        break;
    case CAMERA_FORMAT_BRIGHTNESS:
        *pwParam = m_RecorderFormat.m_iBrightness;
        break;
    case CAMERA_FORMAT_CONTRAST:
        *pwParam = m_RecorderFormat.m_iContrast;
        break;
    case CAMERA_FORMAT_SHARPNESS:
        *pwParam = m_RecorderFormat.m_iSharpness;
        break;
    case CAMERA_FORMAT_WHITEBALANCE:
        *pwParam = m_RecorderFormat.m_iWhiteBalance;
        break;
    case CAMERA_FORMAT_COLORMODE:
        *pwParam = m_RecorderFormat.m_iColorMode;
        break;
    case CAMERA_FORMAT_SCENEMODE:
        *pwParam = m_RecorderFormat.m_iSceneMode;
        break;
    case CAMERA_FORMAT_FLASH:
        *pwParam = m_RecorderFormat.m_iFlash;
        break;
    case CAMERA_FORMAT_EXPOSURE_COMPENSATION:
        *pwParam = m_RecorderFormat.m_iExposureCompensation;
        break;
    default:
        ec = E_NOT_IMPLEMENTED;
        break;
    }

    return ec;
}

ECode  VideoRecorder::SetVideoRecorderFormat(
                        unsigned int nID, int wParam, int lParam)
{
    ECode ec = NOERROR;

    switch (nID) {
    case CAMERA_FORMAT_VIDEO_RESOLUTION:
        if (wParam > 2) wParam = 2;
        if (wParam < 0) wParam = 0;
        if(wParam != m_RecorderFormat.m_iVideoResolution){
            WaitForSingleObject(m_hRecordLock, (DWORD)INFINITE);
            m_RecorderFormat.m_iVideoResolution = wParam;
            ec = SetRecordSize();
            ReleaseMutex(m_hRecordLock);
        }
        break;
    case CAMERA_FORMAT_ZOOM:
        {
            int zoom = wParam / lParam;
            if (zoom < 1 || zoom > DIGITALZOOM_MAX){
                zoom = 1;
            }
            m_RecorderFormat.m_iZoom = zoom;
            m_pConverter->SetDigitalZoom(zoom);
            m_pShower->SetDigitalZoom(zoom);
        }
        break;
    case CAMERA_FORMAT_BRIGHTNESS:
        if (wParam > 255) wParam = 255;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iBrightness = wParam;
        break;
    case CAMERA_FORMAT_CONTRAST:
        if (wParam > 99) wParam = 99;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iContrast = wParam;
        break;
    case CAMERA_FORMAT_SHARPNESS:
        if (wParam > 99) wParam = 99;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iSharpness = wParam;
        break;
    case CAMERA_FORMAT_WHITEBALANCE:
        if (wParam > 99) wParam = 99;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iWhiteBalance = wParam;
        break;
    case CAMERA_FORMAT_COLORMODE:
        if (wParam > 6) wParam = 6;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iColorMode = wParam;
        break;
    case CAMERA_FORMAT_SCENEMODE:
        if (wParam > 5) wParam = 5;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iSceneMode = wParam;
        break;
    case CAMERA_FORMAT_EXPOSURE_COMPENSATION:
        m_RecorderFormat.m_iExposureCompensation = wParam;
        break;
    case CAMERA_FORMAT_FLASH:
        if (wParam > 4) wParam = 4;
        if (wParam < 0) wParam = 0;
        m_RecorderFormat.m_iFlash = wParam;
        break;
    default:
        ec = E_NOT_IMPLEMENTED;
        break;
    }

    return ec;
}

void VideoRecorder::GetRecordDest(YCbCrBufferInfo *pYCbCrInfo)
{
    unsigned long YChannelSize;
    int iCurrentAd;

    YChannelSize    = m_ulRecordBufFrameLen / 2;
    iCurrentAd      = GET_VIDEOBUFFER_ID(m_uInFrameID) * m_ulRecordBufFrameLen;
    pYCbCrInfo->pY  = (BYTE *)(&m_pRecordFramedBuf[iCurrentAd]);
    pYCbCrInfo->pCb = pYCbCrInfo->pY + YChannelSize;
    pYCbCrInfo->pCr = pYCbCrInfo->pCb+ YChannelSize / 2;
}

void VideoRecorderCallBack(void *pFrameBuffer){
    assert(g_CameraDriver.m_pVideoRecorder);
    g_CameraDriver.m_pVideoRecorder->DoRecord(pFrameBuffer);
}

ECode VideoRecorder::BeginRecord(IEvent ** ppOutEvent)
{
    BOOL ret;
    ECode ec = NOERROR;
    m_pCapture->StopCapture();

    WaitForSingleObject(m_hRecordLock, (DWORD)INFINITE);

    ret = m_pShower->SetShowFormat(m_ulRecordFrameWidth,
                                  m_ulRecordFrameHeight);
    if(!ret){
        printf("Error: camera VideoRecorder BeginRecord failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        ec = E_INVALID_OPERATION;
        goto EXIT;
    }


    _CEvent_New(FALSE, UNSIGNALED, &m_pFrameCopyEvent);
    *ppOutEvent = m_pFrameCopyEvent;
    m_pFrameCopyEvent->AddRef();// For *ppOutEvent, user app release it

    m_pCapture->BeginCapture(VideoRecorderCallBack);

EXIT:
    ReleaseMutex(m_hRecordLock);
    return ec;
}

void VideoRecorder::DoRecord(void *pFrameBuffer)
{
    YCbCrBufferInfo YCbCrInfo;

    WaitForSingleObject(m_hRecordLock, (DWORD)INFINITE);

    m_pShower->ShowFrame(pFrameBuffer);

    if (m_uOutFrameID + USED_RECORD_FREAMS > m_uInFrameID) {

        GetRecordDest(&YCbCrInfo);

        m_pConverter->RGBTo422Rotary90((BYTE *)pFrameBuffer, YCbCrInfo);

        m_uInFrameID++;

        if(NULL != m_pFrameCopyEvent){
            m_pFrameCopyEvent->Notify(SIGNALED);
        }
    }

    ReleaseMutex(m_hRecordLock);
}

ECode VideoRecorder::StopRecord()
{
    BOOL ret;
    ECode ec = NOERROR;
    m_pCapture->StopCapture();

    WaitForSingleObject(m_hRecordLock, (DWORD)INFINITE);

    if(NULL != m_pFrameCopyEvent){
        m_pFrameCopyEvent->Release();
        m_pFrameCopyEvent = NULL;
    }

    ret = m_pShower->ResetShowFormat();
    ReleaseMutex(m_hRecordLock);
    assert(ret);
    if(!ret){
        ec = E_INVALID_OPERATION;
    }

    return ec;
}

ECode VideoRecorder::GetRecordFrame(Camera_ImageFrame* pFrame)
{
    unsigned long uSnapFramedBufId = 0;
    unsigned long offset = 0;

    pFrame->nID = m_uOutFrameID;
    //BUGBUG:Should not change resolution. No lock for efficiency.
    pFrame->FrameSize = m_ulRecordBufFrameLen;

    offset = (unsigned long)(GET_VIDEOBUFFER_ID(pFrame->nID)* m_ulRecordBufFrameLen);
    ConvertAllocAddrToId((Address)m_pRecordFramedBuf, offset, &uSnapFramedBufId);
    pFrame->pBuffer = (void *)uSnapFramedBufId;

//    pFrame->pBuffer =
//        (void *)(&m_pRecordFramedBuf[GET_VIDEOBUFFER_ID(pFrame->nID)
//                    *m_ulRecordBufFrameLen]);

    m_uOutFrameID++;

    return NOERROR;
}

//todo:add lock
